/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/

#include "itkCenteredAffineTransform.h"
#include "itkCenteredEuler3DTransform.h"
#include "itkCenteredRigid2DTransform.h"
#include "itkCenteredSimilarity2DTransform.h"
#include "itkEuler2DTransform.h"
#include "itkFixedCenterOfRotationAffineTransform.h"
#include "itkQuaternionRigidTransform.h"
#include "itkRigid3DPerspectiveTransform.h"
#include "itkScaleLogarithmicTransform.h"
#include "itkScaleSkewVersor3DTransform.h"
#include "itkSimilarity3DTransform.h"
#include "itkTranslationTransform.h"
#include "itkAzimuthElevationToCartesianTransform.h"
#include "itkElasticBodyReciprocalSplineKernelTransform.h"
#include "itkElasticBodySplineKernelTransform.h"
#include "itkThinPlateR2LogRSplineKernelTransform.h"
#include "itkThinPlateSplineKernelTransform.h"
#include "itkVolumeSplineKernelTransform.h"
#include "itkIntTypes.h"

#include <algorithm> // For generate.
#include <iterator>  // For begin and end.
#include <random>    // For mt19937.


// Generic Kernel Transform Tester
template <typename KernelType>
int
TestKernelTransform(const char * name, KernelType *)
{
  std::cout << name << std::flush;

  using KernelPointSetType = typename KernelType::PointSetType;

  auto kernel = KernelType::New();
  auto targetLandmarks = KernelPointSetType::New();
  auto sourceLandmarks = KernelPointSetType::New();

  targetLandmarks->GetPoints()->Reserve(4);
  sourceLandmarks->GetPoints()->Reserve(4);

  // Generate some random coordinates
  typename KernelPointSetType::CoordinateType                                 randomCoords[3];
  std::mt19937                                                                randomNumberEngine{};
  std::uniform_real_distribution<typename KernelPointSetType::CoordinateType> randomNumberDistribution(-1.0, 1.0);

  for (int i = 0; i < 4; ++i)
  {
    std::generate(std::begin(randomCoords), std::end(randomCoords), [&randomNumberEngine, &randomNumberDistribution] {
      return randomNumberDistribution(randomNumberEngine);
    });
    targetLandmarks->GetPoints()->SetElement(i, randomCoords);

    std::generate(std::begin(randomCoords), std::end(randomCoords), [&randomNumberEngine, &randomNumberDistribution] {
      return randomNumberDistribution(randomNumberEngine);
    });
    sourceLandmarks->GetPoints()->SetElement(i, randomCoords);
  }

  kernel->SetSourceLandmarks(sourceLandmarks);
  kernel->SetTargetLandmarks(targetLandmarks);

  const itk::ModifiedTimeType         beginMTime = kernel->GetMTime();
  typename KernelType::ParametersType kernelParams = kernel->GetParameters();
  kernelParams[0] = 1.0;
  kernel->SetParameters(kernelParams);
  const itk::ModifiedTimeType endMTime = kernel->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
    return 0;
  }

  std::cout << "FAIL" << std::endl;
  return 1;
}

// Main Program
int
itkTransformsSetParametersTest(int, char *[])
{
  std::cout << "Begin testing of SetParameters() method for all itkTransforms" << std::endl << std::endl;

  std::cout << "AffineTransform->SetParameters() - " << std::flush;
  using Affine = itk::AffineTransform<double, 3>;
  auto                   affine = Affine::New();
  itk::ModifiedTimeType  beginMTime = affine->GetMTime();
  Affine::ParametersType affineParams = affine->GetParameters();
  affineParams[0] = 1.0;
  affine->SetParameters(affineParams);
  itk::ModifiedTimeType endMTime = affine->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }


  std::cout << "CenteredAffineTransform->SetParameters() - " << std::flush;
  using CenteredAffine = itk::CenteredAffineTransform<double, 3>;
  auto centeredAffine = CenteredAffine::New();
  beginMTime = centeredAffine->GetMTime();
  CenteredAffine::ParametersType centeredAffineParams = centeredAffine->GetParameters();
  centeredAffineParams[0] = 1.0;
  centeredAffine->SetParameters(centeredAffineParams);
  endMTime = centeredAffine->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "CenteredEuler3DTransform->SetParameters() - " << std::flush;
  using CenteredEuler3D = itk::CenteredEuler3DTransform<double>;
  auto centeredEuler3D = CenteredEuler3D::New();
  beginMTime = centeredEuler3D->GetMTime();
  CenteredEuler3D::ParametersType centeredEuler3DParams = centeredEuler3D->GetParameters();
  centeredEuler3DParams[0] = 1.0;
  centeredEuler3D->SetParameters(centeredEuler3DParams);
  endMTime = centeredEuler3D->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "CenteredRigid2DTransform->SetParameters() - " << std::flush;
  using CenteredRigid2D = itk::CenteredRigid2DTransform<double>;
  auto centeredRigid2D = CenteredRigid2D::New();
  beginMTime = centeredRigid2D->GetMTime();
  CenteredRigid2D::ParametersType centeredRigid2DParams = centeredRigid2D->GetParameters();
  centeredRigid2DParams[0] = 1.0;
  centeredRigid2D->SetParameters(centeredRigid2DParams);
  endMTime = centeredRigid2D->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "CenteredSimilarity2DTransform->SetParameters() - " << std::flush;
  using CenteredSimilarity2D = itk::CenteredSimilarity2DTransform<double>;
  auto centeredSimilarity2D = CenteredSimilarity2D::New();
  beginMTime = centeredSimilarity2D->GetMTime();
  CenteredSimilarity2D::ParametersType centeredSimilarity2DParams = centeredSimilarity2D->GetParameters();
  centeredSimilarity2DParams[0] = 1.0;
  centeredSimilarity2D->SetParameters(centeredSimilarity2DParams);
  endMTime = centeredSimilarity2D->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "Euler2DTransform->SetParameters() - " << std::flush;
  using Euler2D = itk::Euler2DTransform<double>;
  auto euler2D = Euler2D::New();
  beginMTime = euler2D->GetMTime();
  Euler2D::ParametersType euler2DParams = euler2D->GetParameters();
  euler2DParams[0] = 1.0;
  euler2D->SetParameters(euler2DParams);
  endMTime = euler2D->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "Euler3DTransform->SetParameters() - " << std::flush;
  using Euler3D = itk::Euler3DTransform<double>;
  auto euler3D = Euler3D::New();
  beginMTime = euler3D->GetMTime();
  Euler3D::ParametersType euler3DParams = euler3D->GetParameters();
  euler3DParams[0] = 1.0;
  euler3D->SetParameters(euler3DParams);
  endMTime = euler3D->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "FixedCenteredAffineTransform->SetParameters() - " << std::flush;
  using FixedCenteredAffine = itk::FixedCenterOfRotationAffineTransform<double, 3>;
  auto fixedCenteredAffine = FixedCenteredAffine::New();
  beginMTime = fixedCenteredAffine->GetMTime();
  FixedCenteredAffine::ParametersType fixedCenteredAffineParams = fixedCenteredAffine->GetParameters();
  fixedCenteredAffineParams[0] = 1.0;
  fixedCenteredAffine->SetParameters(fixedCenteredAffineParams);
  endMTime = fixedCenteredAffine->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }


  std::cout << "QuaternionRigidTransform->SetParameters() - " << std::flush;
  using QuaternionRigid = itk::QuaternionRigidTransform<double>;
  auto quaternionRigid = QuaternionRigid::New();
  beginMTime = quaternionRigid->GetMTime();
  QuaternionRigid::ParametersType quaternionRigidParams = quaternionRigid->GetParameters();
  quaternionRigidParams[0] = 1.0;
  quaternionRigid->SetParameters(quaternionRigidParams);
  endMTime = quaternionRigid->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "Rigid2DTransform->SetParameters() - " << std::flush;
  using Rigid2D = itk::Rigid2DTransform<double>;
  auto rigid2D = Rigid2D::New();
  beginMTime = rigid2D->GetMTime();
  Rigid2D::ParametersType rigid2DParams = rigid2D->GetParameters();
  rigid2DParams[0] = 1.0;
  rigid2D->SetParameters(rigid2DParams);
  endMTime = rigid2D->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }


  std::cout << "Rigid3DPerspectiveTransform->SetParameters() - " << std::flush;
  using Rigid3DPerspective = itk::Rigid3DPerspectiveTransform<double>;
  auto rigid3DPerspective = Rigid3DPerspective::New();
  beginMTime = rigid3DPerspective->GetMTime();
  Rigid3DPerspective::ParametersType rigid3DPerspectiveParams = rigid3DPerspective->GetParameters();
  rigid3DPerspectiveParams[0] = 1.0;
  rigid3DPerspective->SetParameters(rigid3DPerspectiveParams);
  endMTime = rigid3DPerspective->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "ScalableAffineTransform->SetParameters() - " << std::flush;
  using ScalableAffine = itk::ScalableAffineTransform<double, 3>;
  auto scalableAffine = ScalableAffine::New();
  beginMTime = scalableAffine->GetMTime();
  ScalableAffine::ParametersType scalableAffineParams = scalableAffine->GetParameters();
  scalableAffineParams[0] = 1.0;
  scalableAffine->SetParameters(scalableAffineParams);
  endMTime = scalableAffine->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "ScaleLogarithmicTransform->SetParameters() - " << std::flush;
  using ScaleLogarithmic = itk::ScaleLogarithmicTransform<double, 3>;
  auto scaleLogarithmic = ScaleLogarithmic::New();
  beginMTime = scaleLogarithmic->GetMTime();
  ScaleLogarithmic::ParametersType scaleLogarithmicParams = scaleLogarithmic->GetParameters();
  scaleLogarithmicParams[0] = 1.0;
  scaleLogarithmic->SetParameters(scaleLogarithmicParams);
  endMTime = scaleLogarithmic->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "ScaleSkewVersor3DTransform->SetParameters() - " << std::flush;
  using ScaleSkewVersor3D = itk::ScaleSkewVersor3DTransform<double>;
  auto scaleSkewVersor3D = ScaleSkewVersor3D::New();
  beginMTime = scaleSkewVersor3D->GetMTime();
  ScaleSkewVersor3D::ParametersType scaleSkewVersor3DParams = scaleSkewVersor3D->GetParameters();
  scaleSkewVersor3DParams[0] = 1.0;
  scaleSkewVersor3D->SetParameters(scaleSkewVersor3DParams);
  endMTime = scaleSkewVersor3D->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "ScaleTransform->SetParameters() - " << std::flush;
  using Scale = itk::ScaleTransform<double, 3>;
  auto scale = Scale::New();
  beginMTime = scale->GetMTime();
  Scale::ParametersType scaleParams = scale->GetParameters();
  scaleParams[0] = 1.0;
  scale->SetParameters(scaleParams);
  endMTime = scale->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "Similarity2DTransform->SetParameters() - " << std::flush;
  using Similarity2D = itk::Similarity2DTransform<double>;
  auto similarity2D = Similarity2D::New();
  beginMTime = similarity2D->GetMTime();
  Similarity2D::ParametersType similarity2DParams = similarity2D->GetParameters();
  similarity2DParams[0] = 1.0;
  similarity2D->SetParameters(similarity2DParams);
  endMTime = similarity2D->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "Similarity3DTransform->SetParameters() - " << std::flush;
  using Similarity3D = itk::Similarity3DTransform<double>;
  auto similarity3D = Similarity3D::New();
  beginMTime = similarity3D->GetMTime();
  Similarity3D::ParametersType similarity3DParams = similarity3D->GetParameters();
  similarity3DParams[0] = 1.0;
  similarity3D->SetParameters(similarity3DParams);
  endMTime = similarity3D->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "TranslationTransform->SetParameters() - " << std::flush;
  using Translation = itk::TranslationTransform<double, 3>;
  auto translation = Translation::New();
  beginMTime = translation->GetMTime();
  Translation::ParametersType translationParams = translation->GetParameters();
  translationParams[0] = 1.0;
  translation->SetParameters(translationParams);
  endMTime = translation->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "VersorTransform->SetParameters() - " << std::flush;
  using Versor = itk::VersorTransform<double>;
  auto versor = Versor::New();
  beginMTime = versor->GetMTime();
  Versor::ParametersType versorParams = versor->GetParameters();
  versorParams[0] = 1.0;
  versor->SetParameters(versorParams);
  endMTime = versor->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "AzimuthElevationToCartesianTransform->SetParameters() - " << std::flush;
  using AzimuthElevationToCartesian = itk::AzimuthElevationToCartesianTransform<double, 3>;
  auto azimuthElevation = AzimuthElevationToCartesian::New();
  beginMTime = azimuthElevation->GetMTime();
  AzimuthElevationToCartesian::ParametersType azimuthElevationParams = azimuthElevation->GetParameters();
  azimuthElevationParams[0] = 1.0;
  azimuthElevation->SetParameters(azimuthElevationParams);
  endMTime = azimuthElevation->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }

  std::cout << "VersorRigid3DTransform->SetParameters() - " << std::flush;
  using VersorRigid3D = itk::VersorRigid3DTransform<double>;
  auto versorRigid3D = VersorRigid3D::New();
  beginMTime = versorRigid3D->GetMTime();
  VersorRigid3D::ParametersType versorRigid3DParams = versorRigid3D->GetParameters();
  versorRigid3DParams[0] = 1.0;
  versorRigid3D->SetParameters(versorRigid3DParams);
  endMTime = versorRigid3D->GetMTime();
  if (endMTime > beginMTime)
  {
    std::cout << "PASS" << std::endl;
  }
  else
  {
    std::cout << "FAIL" << std::endl;
  }


  std::cout << "BSplineTransform->SetParameters() - Not Tested (manual check indicates PASS)" << std::endl;
  //    using BSplineDeformable = itk::BSplineTransform< double >;
  //    auto bSplineDeformable = BSplineDeformable::New();
  //    beginMTime = bSplineDeformable->GetMTime();
  //    bSplineDeformable->SetIdentity();
  //    BSplineDeformable::ParametersType bSplineDeformableParams; = bSplineDeformable->GetParameters();
  //    bSplineDeformableParams[0] = 1.0;
  //    bSplineDeformable->SetParameters( bSplineDeformableParams );
  //   endMTime = bSplineDeformable->GetMTime();
  //   if ( endMTime > beginMTime)
  //     std::cout << "PASS" << std::endl;
  //   else
  //     std::cout << "FAIL" << std::endl;

  TestKernelTransform("ElasticBodyReciprocalSplineKernelTransform->SetParameters() -",
                      static_cast<itk::ElasticBodyReciprocalSplineKernelTransform<double, 3> *>(nullptr));
  TestKernelTransform("ElasticBodySplineKernelTransform->SetParameters() - ",
                      static_cast<itk::ElasticBodySplineKernelTransform<double, 3> *>(nullptr));

  TestKernelTransform("KernelTransform->SetParameters() - ", static_cast<itk::KernelTransform<double, 3> *>(nullptr));

  TestKernelTransform("ThinPlateR2LogRSplineKernelTransform->SetParameters() - ",
                      static_cast<itk::ThinPlateR2LogRSplineKernelTransform<double, 3> *>(nullptr));

  TestKernelTransform("ThinPlateSplineKernelTransform->SetParameters() - ",
                      static_cast<itk::ThinPlateSplineKernelTransform<double, 3> *>(nullptr));

  TestKernelTransform("VolumeSplineKernelTransform->SetParameters() - ",
                      static_cast<itk::VolumeSplineKernelTransform<double, 3> *>(nullptr));

  std::cout << std::endl << "Done." << std::endl;

  return EXIT_SUCCESS;
}
